enum FnType { Map, Filter }

interface SeqFn extends Function {
    TYPE?: FnType
}

class Seq<T> implements Iterable<T> {
    __iter: Iterable<T>
    __chain: Array<SeqFn> = []

    constructor(iter: Iterable<T>) {
        this.__iter = iter
    }

    [Symbol.iterator](): Iterator<T> {
        let { __iter: iter, __chain: chain } = this

        return (function* (): Iterator<T> {
            iterator: for (let t of iter) {
                let v: T = t, i = 0
                for (let fn of chain) {
                    switch (fn.TYPE) {
                        case FnType.Filter:
                            if (!fn(v, i)) continue iterator
                            break;
                        case FnType.Map:
                            v = fn(v, i)
                            break;
                        default:
                    }
                }
                yield v
                i++
            }
        })()
    }

    static is<T>(t: any): t is Seq<T> {
        return Array.isArray(t.__chain)
    }

    static from<T>(t: Iterable<T>): Seq<T> {
        return Seq.is<T>(t) ? t : (new Seq<T>(t))
    }

    valueOf(): Array<T> {
        return Array.from(this)
    }

    map: <R>(fn: SeqFn) => Seq<R> = (fn: SeqFn) => this._chain(fn, FnType.Map)
    filter: (fn: SeqFn) => Seq<T> = (fn: SeqFn) => this._chain(fn, FnType.Filter)

    _chain(fn: SeqFn, t: FnType): this {
        fn.TYPE = t
        this.__chain.push(fn)
        return this
    }

    toStirng(): string {
        return this.valueOf().toString()
    }
}

type MapFn<T, R> = (t: T, i?: number) => R
type FilterFn<T> = (t: T, i?: number) => boolean
type ReduceFn<T, R> = (p: R, t: T, i?: number, iter?: Iterable<T>) => R


export function map<T, R>(fn: MapFn<T, R>, seq: Iterable<T>): Seq<R>;
export function map<T, R>(fn: MapFn<T, R>): (seq: Iterable<T>) => Seq<R>;

export function map<T, R>(fn: MapFn<T, R>, seq?: Iterable<T>): ((seq: Iterable<T>) => Seq<R>) | Seq<R> {

    if (!seq) return (seq: Iterable<T>): Seq<R> => Seq.from(seq).map<R>(fn)

    return Seq.from(seq).map<R>(fn)
}

export function filter<T>(fn: FilterFn<T>, seq: Iterable<T>): Seq<T>;
export function filter<T>(fn: FilterFn<T>): (seq: Iterable<T>) => Seq<T>;

export function filter<T>(fn: FilterFn<T>, seq?: Iterable<T>): ((seq: Iterable<T>) => Seq<T>) | Seq<T> {
    if (!seq) return (seq: Iterable<T>): Seq<T> => Seq.from(seq).filter(fn)

    return Seq.from(seq).filter(fn)
}

export function reduce<T, R>(fn: ReduceFn<T, R>, init: R, seq: Iterable<T>): R
export function reduce<T, R>(fn: ReduceFn<T, R>, init: R): (seq: Iterable<T>) => R

export function reduce<T, R>(fn: ReduceFn<T, R>, init: R, seq?: Iterable<T>): R | ((seq: Iterable<T>) => R) {
    if (!seq) return (seq: Iterable<T>) => reduce(fn, init, seq)
    let i = 0
    for (let t of seq) {
        init = fn(init, t, i, seq)
        i++
    }
    return init
}

export function take<T>(len?: number): (seq: Iterable<T>) => Array<T>
export function take<T>(len?: number, seq?: Iterable<T>): Array<T>

export function take<T>(len?: number, seq?: Iterable<T>): Array<T> | ((seq: Iterable<T>) => Array<T>) {
    let result: Array<T> = []

    if (len && seq) {
        for (let t of seq) result.push(t)
        return result
    }

    if (!len || !seq) return (seq: Iterable<T>) => take(len, seq)

    return Array.from(seq)
}

export default Seq